/**
 * 
 */
package util;

import codec.JPEGQuantizationTable;
import image.Image;

/**
 * @author nquangtrung
 *
 */
public class Transformer {
	public static final String TAG = "Transformer";
	/**
	 * JPEG standard quantization array
	 */
	public static final double[][] q = {{16, 11, 10, 16, 24,  40,  51,  61  }, 
										{12, 12, 14, 19, 26,  58,  60,  55  }, 
										{14, 13, 16, 24, 40,  57,  69,  56  },
										{14, 17, 22, 29, 51,  87,  80,  62  },
										{18, 22, 37, 56, 68,  109, 103, 77  },
										{24, 35, 55, 64, 81,  104, 113, 92  },
										{49, 64, 78, 87, 103, 121, 120, 101 },
										{72, 92, 95, 98, 112, 100, 103, 99  }};
	public int lastNonZero(int[] z, int length) {
		int index = length - 1;
		while (index >= 0 && z[index] == 0)
			index--;
		return (index>=0)?index:0;
	}
	public int lastNonZero(byte[] z, int length) {
		int index = length - 1;
		while (index >= 0 && z[index] == 0)
			index--;
		return (index>=0)?index:0;
	}
	public int[] zigzag(int[][] q, int width, int height) {
		int size = width * height;
		int[] z = new int[size];
		int index = 0;
		int x = 0, y = 0;
		int dx = -1;
		int dy = 1;
		
		while (index < size) {
			// System.out.println("x = " + x + " y = " + y);
			z[index] = q[x][y];
			if (y == 0 || y == height - 1) {
				x++;
				// System.out.println("x = " + x + " y = " + y);
				if (x == width) {
					x--;
				} else {
					index++;
					z[index] = q[x][y];
					if (y == 0) {
						dx = -1;
						dy = 1;
					} else {
						dx = 1;
						dy = -1;
					}
				}
			} else if (x == 0 || x == width - 1) {
				y++;
				// System.out.println("x = " + x + " y = " + y);
				if (y == height) {
					y--;
				} else {
					index++;
					z[index] = q[x][y];
					if (x == 0) {
						dx = 1;
						dy = -1;
					} else {
						dx = -1;
						dy = 1;
					}
				}
			}
			x += dx;
			y += dy;
			index++;
		}
		return z;
	}
	/*
	public static int[] zigzagOrderX = {};
	public static int[] zigzagOrderY = {};
	public byte[] zigzag(byte[][] q, int width, int height) {
		int size = width * height;
		byte[] z = new byte[size];
		
		
		
		return z;
	} */
 
	public byte[] zigzag(byte[][] q, int width, int height) {
		int size = width * height;
		byte[] z = new byte[size];
		int index = 0;
		int x = 0, y = 0;
		int dx = -1;
		int dy = 1;
		//System.out.println("x = " + x + " y = " + y);
		while (index < size) {
			z[index] = q[x][y];
			if (y == 0 || y == height - 1) {
				x++;
				//System.out.println("x = " + x + " y = " + y);
				if (x == width) {
					x--;
				} else {
					index++;
					//System.out.println("x = " + x + " y = " + y);
					z[index] = q[x][y];
					if (y == 0) {
						dx = -1;
						dy = 1;
					} else {
						dx = 1;
						dy = -1;
					}
				}
			} else if (x == 0 || x == width - 1) {
				y++;
				//System.out.println("x = " + x + " y = " + y);
				if (y == height) {
					y--;
				} else {
					index++;
					//System.out.println("x = " + x + " y = " + y);
					z[index] = q[x][y];
					if (x == 0) {
						dx = 1;
						dy = -1;
					} else {
						dx = -1;
						dy = 1;
					}
				}
			}
			x += dx;
			y += dy;
			index++;
		}
		return z;
	}
	public int[][] quantize(double[][] g, int width, int height, byte[][] quantizeTable, double quality) {
		int[][] r = new int[width][height];
		// Log.debugArray(TAG, "Table: ", g, 8, 8);
		// Log.debugArray(TAG, "Quantization Table: ", quantizeTable, 8, 8);
		byte[][] q = JPEGQuantizationTable.getQuantizationTable(quantizeTable, quality);
		for (int x = 0; x < width; x++) {
			for (int y = 0; y < height; y++) {
				r[x][y] = (int) Math.round(g[x][y] / (double)q[x][y]);
				if (q[x][y] == 55) {
					// System.out.println("(" + x + ", " + y + ")" + g[x][y] + "/" + q[x][y] + " = " + r[x][y]);
					// System.out.printf("(%d,%d) %f/%d=%d\n", x, y, g[x][y], q[x][y], r[x][y]);
				}
			}
		}
		return r;
	}
	public int[][] quantize(double[][] g, int width, int height, byte[][] quantizeTable) {
		return quantize(g, width, height, quantizeTable, 1);
	}
	
	public int[][][] quantize(double[][][] g, int width, int height, double quality) {
		int[][][] r = new int[3][width][height];
		for (int i = 0; i < 3; i++) {
			r[i] = quantize(g[i], width, height, 
					(i == 0)?JPEGQuantizationTable.LUMINANCE:JPEGQuantizationTable.CHROMINANCE, 
					quality);
		}
		return r;
	}
	
	public int[][] dequantize(int[][] b, int width, int height, byte[][] quantizeTable, double quality) {
		int[][] g = new int[width][height];
		byte[][] q = JPEGQuantizationTable.getQuantizationTable(quantizeTable, quality);
		
		for (int x = 0; x < width; x++) {
			for (int y = 0; y < height; y++) {
				g[x][y] = b[x][y] * q[x][y];
			}
		}
		
		return g;
	}
	public int[][] reverseTransform(int[][] g, int width, int height) {
		int[][] ycc = new int[width][height];
		
		double pidiv8 = Math.PI/8;
		for (int x = 0; x < 8; x++) {
			for (int y = 0; y < 8; y++) {
				int yccxy = 0; 
				for (int u = 0; u < 8; u++) {
					double alphau = (u == 0)?Math.sqrt(1.0/8.0):Math.sqrt(1.0/4.0);
					for (int v = 0; v < 8; v++) {
						double alphav = (v == 0)?Math.sqrt(1.0/8.0):Math.sqrt(1.0/4.0);
						
						yccxy += alphau * alphav * g[u][v] * Math.cos(pidiv8*(x + 1/2)*u) * Math.cos(pidiv8*(y + 1/2)*v);
					}
				}
				
				ycc[x][y] = yccxy;
			}
		}
		for (int x = 0; x < 8; x++) {
			for (int y = 0; y < 8; y++) {
				ycc[x][y] += 128;				
			}
		}
		return ycc;
	}
	
	public int[][] reverseTransform(double[][] g, int width, int height) {
		int[][] ycc = new int[width][height];
		
		double pidiv8 = Math.PI/8;
		for (int x = 0; x < 8; x++) {
			for (int y = 0; y < 8; y++) {
				int yccxy = 0; 
				for (int u = 0; u < 8; u++) {
					double alphau = (u == 0)?Math.sqrt(1.0/8.0):Math.sqrt(1.0/4.0);
					for (int v = 0; v < 8; v++) {
						double alphav = (v == 0)?Math.sqrt(1.0/8.0):Math.sqrt(1.0/4.0);
						
						yccxy += alphau * alphav * g[u][v] * Math.cos(pidiv8*(x + 1/2)*u) * Math.cos(pidiv8*(y + 1/2)*v);
					}
				}
				
				ycc[x][y] = yccxy;
			}
		}
		
		for (int x = 0; x < 8; x++) {
			for (int y = 0; y < 8; y++) {
				ycc[x][y] += 128;				
			}
		}
		
		return ycc;
	}
	public double[][][] ycctransform(Image image) {
		Log.d(TAG, "Attemp to apply the DCT transformation on an YCbCr image.");
		int width = image.getWidth();
		int height = image.getHeight();
		
		ColorTransformer ct = new ColorTransformer();
		int[][][] ycc = ct.rgb2ycc(image);
		int[][][] yccdown = ct.yccdownsample(ycc, image.getWidth(), image.getHeight());
		
		double[][][] g = new double[3][width][height]; // rgb
		for (int i = 0; i < 3; i++) {
			g[i] = this.transform(yccdown[i], width, height);
		}
		
		return g;
	}
	public double[][][] transform(Image image) {
		Log.d(TAG, "Attemp to make the DCT transformation on an image.");
		int width = image.getWidth();
		int height = image.getHeight();
		double[][][] g = new double[3][width][height]; // rgb
		
		int red[][] = new int[width][height];
		int green[][] = new int[width][height];
		int blue[][] = new int[width][height];
		for (int x = 0; x < width; x++) {
			for (int y = 0; y < width; y++) {
				red[x][y] = image.getRed(x, y);
				green[x][y] = image.getGreen(x, y);
				blue[x][y] = image.getBlue(x, y);
			}
		}
		
		g[0] = this.transform(red, width, height);
		g[1] = this.transform(green, width, height);
		g[2] = this.transform(blue, width, height);
		
		return g;
	}
	/**
	 * http://en.wikipedia.org/wiki/JPEG#Discrete_cosine_transform
	 */
	public double[][] transform(int[][] original, int width, int height) {
		Log.d(TAG, "Attemp to make the DCT transformation.");
		double[][] g = new double[width][height];
		
		// shift the original value from a positive range to one centered 
		// around zero
		int[][] sub = new int[width][height];
		for (int x = 0; x < width; x++) {
			for (int y = 0; y < width; y++) {
				sub[x][y] = (int) (original[x][y] - 128);
			}
		}
		
		int N = 8;
		// init coef
        double[][] c = new double[N][N];
        for (int i = 1; i < N; i++) {
        	for (int j = 1;j < 8;j++) {
        		c[i][j] = 1;
        	}
        }
        for (int i=0; i < N; i++) {
        	c[i][0] = 1/Math.sqrt(2.0);
        	c[0][i] = 1/Math.sqrt(2.0);
        }
        c[0][0] = 0.5;
		
		// transform
        for (int u = 0; u < N; u++) {
        	for (int v = 0; v < N; v++) {
        		double sum = 0.0;
        		for (int x = 0; x < N ; x++) {
        			for (int y = 0; y < N; y++) {
        				sum += sub[x][y]* Math.cos(((2*x+1)/(2.0*N))*u*Math.PI) * 
        						Math.cos(((2*y+1)/(2.0*N))*v*Math.PI);
        			}
        		}
        		sum *= c[u][v]/4.0;
        		g[u][v] = sum;
        	}
        }
		
		return g;
	}
}
